﻿using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using DotNet.DevOps.Tasks.Util;

namespace DotNet.DevOps.Tasks.AssemblyInfo.Updaters.VersionUpdaters
{
    internal abstract class BaseVersionUpdater : PropertyUpdater
    {
        private Dictionary<String, String> properties;
        private readonly Regex regex = new Regex(@"^(?<Major>\d*)((\.(?<Minor>\d*))?(\.(?<Patch>\d*))?(\.(?<Revision>\d*))?)?$", RegexOptions.Compiled | RegexOptions.Singleline);

        private String propertyName { get; set; }

        protected BaseVersionUpdater(AssemblyInfo asm, String propertyName, ILog logger)
            : base(asm, logger)
        {
            this.propertyName = propertyName;
        }

        protected override Dictionary<String, String> GetPropertyNameValuePairs()
        {
            return properties ?? (properties = asm.GetSpecialStringPropertyNameValuePairsByName(propertyName));
        }

        protected override String GetNewEntryValue(String keyName, String oldValue)
        {
            return IsAutoIncrementPossible(oldValue) ? ApplyIncrementToOldValue(oldValue) : ApplyNewPropertyValue(properties[keyName], oldValue);
        }

        private string ApplyIncrementToOldValue(string oldValue)
        {
            oldValue = ApplySanity(oldValue);
            oldValue = IncrementLowestVersionNumber(oldValue);
            ValidateVersion(oldValue);
            return oldValue;
        }

        private string ApplyNewPropertyValue(string newValue, string oldValue)
        {
            if(String.IsNullOrEmpty(newValue) == false)
            {
                newValue = ApplySanity(newValue);
                ValidateVersion(newValue);
                return newValue;
            }
            return oldValue;
        }


        private string ApplySanity(string newValue)
        {
            var returnValue = newValue;
            if (String.IsNullOrEmpty(newValue) == false && newValue.Contains("*"))
            {
                returnValue = newValue.Replace("*", "0");
                Log.LogWarning(string.Format("Sanity was applied to {0}, which is now {1}.", newValue, returnValue));
            }
            return returnValue;
        }

        private bool IsAutoIncrementPossible(String oldValue)
        {
            return (String.IsNullOrEmpty(oldValue) == false && ShouldAutoIncrementLastVersionNumber());
        }

        protected abstract bool ShouldAutoIncrementLastVersionNumber();

        private string IncrementLowestVersionNumber(string newValue)
        {
            if(regex.IsMatch(newValue) == false)
            {
                throw new Exception(String.Format("{0} is not a legal version number, please use on of 'X', 'Y.Y', 'Z.Z.Z', 'V.V.V.V' where characters is replaced by numeric numbers.", newValue));
            }

            var versions = GetVersionNumbers(newValue);

            versions[versions.Count - 1] = versions[versions.Count - 1] + 1;

            return GenerateNewVersionString(versions);
        }

        private List<int> GetVersionNumbers(String newValue)
        {
            var numbers = new List<int>();

            var groups = regex.Match(newValue).Groups;

            if (String.IsNullOrEmpty(groups["Major"].Value) == false)   numbers.Add(int.Parse(groups["Major"].Value));
            if (String.IsNullOrEmpty(groups["Minor"].Value) == false)   numbers.Add(int.Parse(groups["Minor"].Value));
            if (String.IsNullOrEmpty(groups["Patch"].Value) == false)   numbers.Add(int.Parse(groups["Patch"].Value));
            if (String.IsNullOrEmpty(groups["Revision"].Value) == false)numbers.Add(int.Parse(groups["Revision"].Value));

            return numbers;
        }

        private static String GenerateNewVersionString(List<int> versionNumbers)
        {
            var newVersion = "";
            versionNumbers.ForEach(number => newVersion = string.Format("{0}{1}.", newVersion, number));
            return newVersion.Remove(newVersion.Length - 1);
        }

        private void ValidateVersion(string version)
        {
            ValidateThatAllAreNumeric(version);
            ValidateUpperNumberLimit(version);
        }

        private void ValidateThatAllAreNumeric(string version)
        {
            if (regex.IsMatch(version) == false)
            {
                throw new Exception(String.Format("'{0}' is not a legal version number, please use on of 'X', 'Y.Y', 'Z.Z.Z', 'V.V.V.V' where characters is replaced by numeric numbers.", version));
            }
        }

        private static void ValidateUpperNumberLimit(string version)
        {
            var values = version.Split(new string[] { "." }, StringSplitOptions.RemoveEmptyEntries);

            foreach(var value in values)
            {
                if(int.Parse(value) > (UInt16.MaxValue - 1))
                {
                    throw new Exception(String.Format("Version '{0}', has reached upper allowed limit ({1}) for one of its of components. Please take appropiate action.", version, UInt16.MaxValue));
                }
            }
        }
    }
}
